use super::config;
use actix_web::http::header::{
    Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
};
use actix_web::{web, HttpRequest, HttpResponse};
use lazy_static::lazy_static;
use log::info;
use mongodb::sync::{Client, Collection};
use mongodb::{
    bson::{doc, Bson, Document},
    options::FindOptions,
};

use serde::{Deserialize, Serialize};
use serde_json::Value;
use sourcemap::{decode_slice, DecodedMap, RewriteOptions, SourceMap};
use std::fs;
use std::str::FromStr;
use subprocess::Exec;
use subprocess::ExitStatus;

lazy_static! {
    pub static ref MONGO: Client = create_mongo_client();
}

fn create_mongo_client() -> Client {
    let mongo = config::CONFIG.mongo();
    Client::with_uri_str(mongo).expect("初始mongo连接对象失败.")
}

fn collection(coll_name: &str) -> Collection<Document> {
    MONGO.database("logs").collection(coll_name)
}

/// 查询日志的结构体
#[derive(Deserialize, Serialize, Debug)]
pub struct LogQuery {
    name: String,
    detail: String,
    start_time: String,
    end_time: String,
    is_slow: i32,
    uri: String,
    is_failed: i32,
    request_id: String,
}

/// 加载日志请求接口
#[derive(Deserialize, Serialize, Debug)]
pub struct LogLoad {
    pub service_code: String,
    pub log_file: String,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct RemoteLogLoad {
    pub server: String,
    pub server_dir: String,
}

/// 加载日志请求接口
#[derive(Deserialize, Serialize, Debug)]
pub struct ListClientLogs {
    basic_data: BasicData,
    service_code: String,
    list_type: String, //1-服务器日志地址；2-客户端日志地址
}

/// 加载日志请求接口
#[derive(Deserialize, Serialize, Debug)]
pub struct BasicData {
    full_path: String,
}

/// 下载日志请求接口
#[derive(Deserialize, Serialize, Debug)]
pub struct LogDown {
    full_path: String,
    service_code: String,
    list_type: String, //1-服务器日志地址；2-客户端日志地址
}

#[derive(Deserialize, Serialize, Debug)]
pub struct ExtractSql {
    stack: String,
}

/// 下载日志请求接口
#[derive(Deserialize, Serialize, Debug)]
pub struct LogExport {
    select_array: String,
}

/// 前端异常
#[derive(Debug, Serialize, Deserialize)]
pub struct FrontTrace {
    ip: String,
    source: String,
    error: String,
    time: String,
    line: String,
    col: String,
    name: String,
    id: String,
    syscode: String,
}

/// 后端错误
#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceError {
    request_id: String,
    url: String,
    name: String,
    syscode: String,
    ip: String,
    time: String,
    err_msg: String,
}

///过程入参查询
#[derive(Debug, Serialize, Deserialize)]
pub struct ProcParam {
    obj_name: String,
    url: String,
    pwd: String,
    user: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProcInfo {
    pub pos: i64,
    pub name: String,
    pub type_name: String,
    pub object_type: Option<String>,
}

///前端上传信息
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FrontReport {
    name: String,      //操作人
    ip: String,        //ip
    date_time: String, //时间
    sys_code: String,  //系统编号
    page_name: String, //页面名称
    //menu_id: String,   //菜单编号
    title: String,     //标题
    memo: String,      //备注，json字符串
}

///根据query入参，查找mongodb中日志数据
pub async fn list_logs(query: web::Json<LogQuery>) -> HttpResponse {
    let mut docs: Vec<Bson> = Vec::new();
    let mut d: Document = Document::new();

    if !query.name.is_empty() {
        docs.push(Bson::Document(doc! {"user_name": &query.name}));
    }

    if !query.request_id.is_empty() {
        docs.push(Bson::Document(doc! {"request_id": &query.request_id}));
    }

    if !query.detail.is_empty() {
        docs.push(Bson::Document(doc! {
            "$or":[
                {"detail": {"$regex": &query.detail, "$options": "i"}},
                {"request": {"$regex": &query.detail, "$options": "i"}},
                {"response": {"$regex": &query.detail, "$options": "i"}},
                {"stack": {"$regex": &query.detail, "$options": "i"}},
            ]
        }));
    }

    if !query.start_time.is_empty() {
        docs.push(Bson::Document(doc! {"time": {"$gte": &query.start_time}}));
    }

    if !query.end_time.is_empty() {
        docs.push(Bson::Document(doc! {"time": {"$lte": &query.end_time}}));
    }

    if query.is_slow == 1 {
        docs.push(Bson::Document(
            doc! {"time_span": {"$regex": "SLOW", "$options": "i"}},
        ));
    }

    if !query.uri.is_empty() {
        docs.push(Bson::Document(
            doc! {"uri": {"$regex": &query.uri, "$options": "i"}},
        ));
    }

    if query.is_failed == 1 {
        docs.push(Bson::Document(doc! {"status": "500"}));
    }

    if docs.len() > 0 {
        d.insert("$and", Bson::Array(docs));
    }

    info!("查询条件:{}", d.to_string());

    let f = FindOptions::builder()
        .limit(1000)
        .sort(doc! { "time": 1 })
        .build();
    // 构造查询参数
    let coll = collection("log_info");
    let cursor = coll.find(d, f).unwrap();

    let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();

    let mut response_doc = doc! {};
    response_doc.insert("code", 0);
    response_doc.insert("data", docs);
    HttpResponse::Ok().json(response_doc)
}

/// 加载指定的日志到mongodb中，并清空之前的日志记录
pub async fn load_log(query: web::Json<LogLoad>) -> HttpResponse {
    if query.log_file.is_empty() {
        let mut response_doc = doc! {};
        response_doc.insert("code", 1);
        return HttpResponse::Ok().json(response_doc);
    }
    let mut log_file = config::CONFIG.get_service_log(&query.service_code);
    log_file += &query.log_file;
    //info!("加载的系统日志路径{}", log_file);

    //let result = Exec::cmd("log_tool.exe").arg(&log_file).join().unwrap();
    let result = match Exec::cmd("log_tool.exe").arg(&log_file).join() {
        Ok(ok) => ok,
        __ => Exec::cmd("./log_tool").arg(&log_file).join().unwrap(),
    };

    match result {
        ExitStatus::Exited(code) => {
            if code != 0 {
                let mut response_doc = doc! {};
                response_doc.insert("code", 1);
                return HttpResponse::Ok().json(response_doc);
            }
        }
        _ => {
            let mut response_doc = doc! {};
            response_doc.insert("code", 1);
            return HttpResponse::Ok().json(response_doc);
        }
    }

    info!("执行程序返回结果:{:#?}", result);
    let mut response_doc = doc! {};
    response_doc.insert("code", 0);
    HttpResponse::Ok().json(response_doc)
}

/// 列出目前的日志加载信息
pub async fn list_log_options() -> HttpResponse {
    let content = fs::read_to_string("static/api/logOption.json").unwrap();
    let json: Value = serde_json::from_str(&content).unwrap();
    HttpResponse::Ok().json(json)
}

/// 列出菜单
pub async fn list_menu() -> HttpResponse {
    let content = fs::read_to_string("static/api/init.json").unwrap();
    let json: Value = serde_json::from_str(&content).unwrap();
    HttpResponse::Ok().json(json)
}

pub async fn list_dir(query: web::Json<ListClientLogs>) -> HttpResponse {
    if query.service_code.is_empty() {
        let mut response_doc = doc! {};
        response_doc.insert("code", 1);
        return HttpResponse::Ok().json(response_doc);
    }

    let mut root = match query.list_type.as_str() {
        _ => config::CONFIG.get_service_log(&query.service_code), // 默认是服务器日志根目录
    };

    let root_clone = root.clone();
    let root_str = root_clone.as_str();
    info!("root_str:{}", root_str);
    if !query.basic_data.full_path.is_empty() {
        root += &query.basic_data.full_path;
    }
    info!("root:{}", root);

    let paths = fs::read_dir(root);
    let mut response_doc = doc! {};
    let mut dir_doc: Vec<Bson> = Vec::new();

    match paths {
        Ok(dir_infos) => {
            //response_doc.insert("code", 0);
            for path in dir_infos {
                match path {
                    Ok(path) => {
                        let file_type = path.file_type().unwrap();
                        let dir_name = path.file_name().into_string();
                        let dir_path = path.path().clone();
                        let ss = dir_path.to_str().unwrap();

                        match dir_name {
                            Ok(dir_name) => {
                                let mut doc = doc! {
                                    "title": &dir_name,
                                    "basicData": {"full_path": ss.replace(root_str, "")},
                                    "spread": false,
                                };

                                if file_type.is_file() {
                                    doc.insert("last", true);
                                } else if file_type.is_dir() {
                                    doc.insert("last", false);
                                };
                                let children: Vec<Bson> = Vec::new();
                                doc.insert("children", children);
                                dir_doc.push(Bson::Document(doc));
                                //info!("{:#?}", dir_doc);
                            }
                            Err(_) => {
                                response_doc.insert("status", doc! {"code": 1 });
                            }
                        }
                    }
                    Err(_) => {
                        response_doc.insert("status", doc! {"code": 1 });
                    }
                }
            }
        }
        Err(_) => {
            response_doc.insert("code", -1);
        }
    }
    //response_doc.insert("code", 0);
    response_doc.insert("status", doc! {"code": 200 });
    response_doc.insert("data", dir_doc);

    HttpResponse::Ok().json(response_doc)
}

/// 下载日志
pub async fn log_download(query: web::Form<LogDown>) -> HttpResponse {
    info!("request={:?}", query);
    if query.service_code.is_empty() {
        let mut response_doc = doc! {};
        response_doc.insert("code", 1);
        return HttpResponse::Ok().json(response_doc);
    }
    //改用match 语法
    let mut root = match query.list_type.as_str() {
        _ => config::CONFIG.get_service_log(&query.service_code), // 默认是服务器日志下载
    };

    if !query.full_path.is_empty() {
        root += &query.full_path;
    }
    info!("下载文件路径:{}", root);

    let path = std::path::Path::new(&root);
    let file_name = path.file_name().unwrap().to_str().unwrap();

    let mut builder = HttpResponse::Ok();
    let cd1 = ContentDisposition {
        disposition: DispositionType::Attachment,
        parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
            charset: Charset::Ext(String::from("UTF-8")),
            language_tag: None,
            value: file_name.as_bytes().to_vec(),
        })],
    };
    builder.insert_header((actix_web::http::header::CONTENT_DISPOSITION, cd1));
    //
    let content = fs::read(root).unwrap();
    //let stream = actix_web::body::Body::from_slice(&content);
    builder.body(content)
}

/// 导出选择的日志
pub async fn log_export(query: web::Form<LogExport>) -> HttpResponse {
    info!("request={:#?}", query);
    let request_ids: Vec<&str> = query.select_array.split(',').collect();
    let mut docs: Vec<Bson> = Vec::new();
    for v in request_ids {
        docs.push(Bson::Document(doc! {"request_id": &v}));
    }
    let mut d: Document = Document::new();

    if docs.len() > 0 {
        d.insert("$or", Bson::Array(docs));
    }

    info!("导出条件:{}", d);

    let f = FindOptions::builder().limit(200).build();
    // 构造查询参数
    let coll = collection("log_info");
    let cursor = coll.find(d, f).unwrap();

    let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();
    let mut content = String::from("");
    for doc in docs {
        let headers = doc.get_str("headers").unwrap_or("");
        let request = doc.get_str("request").unwrap_or("");
        let response = doc.get_str("response").unwrap_or("");
        let stack = doc.get_str("stack").unwrap_or("");

        let no_stack = format!("\"request\":{},\n\"response\":{}", request, response);
        let no_stack = "{\n".to_owned() + &no_stack + "\n}";

        content = format!(
            "{}\n{}\u{9658}\u{0003}\u{9658}{}\n\u{0003}\u{0003}\u{0003}\n",
            &headers, &no_stack, &stack
        );
    }

    let mut builder = HttpResponse::Ok();
    let cd = ContentDisposition {
        disposition: DispositionType::Attachment,
        parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
            charset: Charset::Ext(String::from("UTF-8")),
            language_tag: None,
            value: b"export.log".to_vec(),
        })],
    };
    builder.insert_header((actix_web::http::header::CONTENT_DISPOSITION, cd));
    //

    //let stream = actix_web::body::Body::from(content);
    builder.body(content)
}

pub async fn list_service_log() -> HttpResponse {
    let mut response_doc = doc! {};
    let dir_doc: Vec<Bson> = config::CONFIG.service_log_list();

    response_doc.insert("code", 0);
    response_doc.insert("data", dir_doc);

    HttpResponse::Ok().json(response_doc)
}

pub async fn extract_sql(stack: web::Json<ExtractSql>) -> HttpResponse {
    let mut response_doc = doc! {};
    let mut sql_flag = false;
    let lines: Vec<&str> = stack.stack.lines().collect();
    //保存当前
    let mut now_sql = String::from("");

    for line in lines {
        if line.contains("JdbcEntry:") || line.contains("jdbcCallTrace:") {
            sql_flag = true;
        } else {
            if !sql_flag {
                continue;
            }
        }
        now_sql = format!("{}\n{}", now_sql, line.trim_start());
        if line.contains("}") {
            sql_flag = false;
            now_sql = format!("{}]", now_sql);
            //now_sql = format!("{}</div></div>", now_sql);
        }
    }
    now_sql = now_sql
        .replace("JdbcEntry:", "")
        .replace("jdbcCallTrace:", "")
        .replace("{", "")
        .replace("}", "")
        .replace("sql:'", "sql:[")
        .replace("', timeSpan:", ", timeSpan:");

    response_doc.insert("code", 0);
    response_doc.insert("sql", now_sql);

    HttpResponse::Ok().json(response_doc)
}

fn load_from_slice(rdr: &[u8]) -> SourceMap {
    match decode_slice(&rdr).unwrap() {
        DecodedMap::Regular(sm) => sm,
        DecodedMap::Index(idx) => idx
            .flatten_and_rewrite(&RewriteOptions {
                load_local_source_contents: true,
                ..Default::default()
            })
            .unwrap(),
        _ => panic!("unexpected sourcemap format"),
    }
}

pub async fn append_front_trace(req: HttpRequest) -> HttpResponse {
    info!("request={:#?}", req.query_string());

    let front_trace = web::Query::<FrontTrace>::from_query(req.query_string()).unwrap();
    info!("打开mongo");
    // 前端异常上报表
    let collection = collection("front_trace");
    let name = &front_trace.name;
    let ip = &front_trace.ip;
    let time = &front_trace.time;
    let source = &front_trace.source;
    let error = &front_trace.error;
    let line = &front_trace.line;
    let col = &front_trace.col;
    let id: &String = &front_trace.id;
    let syscode = &front_trace.syscode;

    //传来时双引号会被转%22
    let convert_source = str::replace(&source, "%22", "\"");
    //info!("convert_source={}", convert_source);

    match serde_json::from_str(&convert_source) {
        Ok(json_source) => {
            match json_source {
                Value::Array(source_array) => {
                    let mut err_strace = String::from("");
                    //
                    for source_node in source_array.iter() {
                        let url = source_node["url"].as_str().unwrap();
                        let map = String::from_str(url).unwrap() + ".map";
                        //info!("map={:#?}", map);

                        let client = reqwest::blocking::Client::new();
                        let body = client.get(map).send().unwrap().bytes().unwrap();

                        let sourcemap = load_from_slice(&body);
                        let line = source_node["line"]
                            .as_str()
                            .unwrap()
                            .parse::<u32>()
                            .unwrap()
                            - 1;
                        let col = source_node["column"]
                            .as_str()
                            .unwrap()
                            .parse::<u32>()
                            .unwrap();
                        let token = sourcemap.lookup_token(line, col).unwrap();
                        if err_strace.len() > 0 {
                            err_strace.push_str("<br>");
                        }
                        err_strace.push_str(&token.to_string());
                    }
                    info!("parsed_source={}", err_strace.to_string());
                    info!("保存到mongo");
                    collection
                        .insert_one(
                            doc! {
                                "ip": ip,
                                "source": err_strace,
                                "error": error,
                                "time": time,
                                //"line": token.get_src_line()+1,
                                //"col": token.get_src_line(),
                                "name": name,
                                "id": id,
                                "syscode":syscode,
                            },
                            None,
                        )
                        .unwrap();
                }
                _ => {}
            }
        }
        Err(_) => {
            // 获取sourcemap,这里怎么做？
            let map = String::from_str(source).unwrap() + ".map";
            info!("map={:#?}", map);

            let client = reqwest::blocking::Client::new();
            let body = client.get(map).send().unwrap().bytes().unwrap();

            let sourcemap = load_from_slice(&body);
            let line = line.parse::<u32>().unwrap() - 1;
            let col = col.parse::<u32>().unwrap();
            let token = sourcemap.lookup_token(line, col).unwrap();

            info!("保存到mongo");
            collection
                .insert_one(
                    doc! {
                        "ip": ip,
                        "source": token.get_source().unwrap(),
                        "error": error,
                        "time": time,
                        "line": token.get_src_line()+1,
                        "col": token.get_src_line(),
                        "name": name,
                        "id": id,
                        "syscode":syscode,
                    },
                    None,
                )
                .unwrap();
        }
    }

    HttpResponse::Ok().body("ok")
}

pub async fn list_front_trace(query: web::Json<FrontTrace>) -> HttpResponse {
    info!("request={:#?}", query);
    let mut docs: Vec<Bson> = Vec::new();
    let mut d: Document = Document::new();

    if !query.name.is_empty() {
        docs.push(Bson::Document(doc! {"name": &query.name}));
    }

    if !query.ip.is_empty() {
        docs.push(Bson::Document(doc! {"ip": &query.ip}));
    }

    if !query.time.is_empty() {
        docs.push(Bson::Document(doc! {"time": {"$gte": &query.time}}));
    }

    if !query.syscode.is_empty() {
        docs.push(Bson::Document(doc! {"syscode": &query.syscode}));
    }

    if docs.len() > 0 {
        d.insert("$and", Bson::Array(docs));
    }

    info!("查询条件:{:#?}", d);

    let f = FindOptions::builder()
        .limit(1000)
        .sort(doc! { "time": -1 })
        .build();
    // 构造查询参数
    let coll = collection("front_trace");
    let cursor = coll.find(d, f).unwrap();

    let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();

    let mut response_doc = doc! {};
    response_doc.insert("code", 0);
    response_doc.insert("data", docs);
    HttpResponse::Ok().json(response_doc)
}

pub async fn append_service_error(req: HttpRequest) -> HttpResponse {
    info!("request={:#?}", req.query_string());

    let service_error = web::Query::<ServiceError>::from_query(req.query_string()).unwrap();
    info!("打开mongo");
    // 后端错误上报
    let collection = collection("service_error");

    info!("保存到mongo");
    collection
        .insert_one(
            doc! {
            "request_id": &service_error.request_id,
            "url": &service_error.url,
            "name": &service_error.name,
            "syscode": &service_error.syscode,
            "ip": &service_error.ip,
            "time": &service_error.time,
            "err_msg": &service_error.err_msg,
            },
            None,
        )
        .unwrap();

    HttpResponse::Ok().body("ok")
}

pub async fn list_service_error(query: web::Json<ServiceError>) -> HttpResponse {
    info!("request={:#?}", query);
    let mut docs: Vec<Bson> = Vec::new();
    let mut d: Document = Document::new();

    if !query.name.is_empty() {
        docs.push(Bson::Document(doc! {"name": &query.name}));
    }

    if !query.ip.is_empty() {
        docs.push(Bson::Document(doc! {"ip": &query.ip}));
    }

    if !query.time.is_empty() {
        docs.push(Bson::Document(doc! {"time": {"$gte": &query.time}}));
    }

    if !query.syscode.is_empty() {
        docs.push(Bson::Document(doc! {"syscode": &query.syscode}));
    }

    if !query.url.is_empty() {
        docs.push(Bson::Document(
            doc! {"uri": {"$regex": &query.url, "$options": "i"}},
        ));
    }

    if !query.err_msg.is_empty() {
        docs.push(Bson::Document(
            doc! {"uri": {"$regex": &query.err_msg, "$options": "i"}},
        ));
    }

    if docs.len() > 0 {
        d.insert("$and", Bson::Array(docs));
    }

    info!("查询条件:{:#?}", d);

    let f = FindOptions::builder()
        .limit(1000)
        .sort(doc! { "time": -1 })
        .build();
    // 构造查询参数
    let coll = collection("service_error");
    let cursor = coll.find(d, f).unwrap();

    let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();

    let mut response_doc = doc! {};
    response_doc.insert("code", 0);
    response_doc.insert("data", docs);
    HttpResponse::Ok().json(response_doc)
}


pub async fn append_front_report(req: HttpRequest) -> HttpResponse {
    info!("request={:#?}", req.query_string());

    let front_report = web::Query::<FrontReport>::from_query(req.query_string()).unwrap();
    info!("打开mongo");
    // 后端错误上报
    let collection = collection("front_report");

    info!("保存到mongo");
    collection
        .insert_one(
            doc! {
                "name": &front_report.name,
                "ip":&front_report.ip,
                "date_time": &front_report.date_time,
                "sys_code": &front_report.sys_code,
                "page_name": &front_report.page_name,
                //"menu_id": &front_report.menu_id,
                "title": &front_report.title,
                "memo": &front_report.memo,
            },
            None,
        )
        .unwrap();

    HttpResponse::Ok().body("ok")
}

pub async fn list_front_report(query: web::Json<FrontReport>) -> HttpResponse {
    info!("request={:#?}", query);
    let mut docs: Vec<Bson> = Vec::new();
    let mut d: Document = Document::new();

    if !query.name.is_empty() {
        docs.push(Bson::Document(doc! {"name": &query.name}));
    }

    if !query.date_time.is_empty() {
        docs.push(Bson::Document(doc! {"date_time": {"$gte": &query.date_time}}));
    }

    if !query.sys_code.is_empty() {
        docs.push(Bson::Document(doc! {"sys_code": &query.sys_code}));
    }

    if !query.title.is_empty() {
        docs.push(Bson::Document(
            doc! {"title": {"$regex": &query.title, "$options": "i"}},
        ));
    }

    if !query.memo.is_empty() {
        docs.push(Bson::Document(doc! {
            "$or":[
                {"memo": {"$regex": &query.memo, "$options": "i"}},
            ]
        }));
    }

    if docs.len() > 0 {
        d.insert("$and", Bson::Array(docs));
    }

    info!("查询条件:{:#?}", d);

    let f = FindOptions::builder()
        .limit(1000)
        .sort(doc! { "date_time": 1 })
        .build();
    // 构造查询参数
    let coll = collection("front_report");
    let cursor = coll.find(d, f).unwrap();

    let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();

    let mut response_doc = doc! {};
    response_doc.insert("code", 0);
    response_doc.insert("data", docs);
    HttpResponse::Ok().json(response_doc)
}
